Skip to content

docs(signing): verify against a counterparty's several agents - #1871

Merged
bokelley merged 1 commit into
nextfrom
feat/1214-brand-json-agents-verification
Nov 8, 2026
Merged

bokelley merged 1 commit into
nextfrom
feat/1214-brand-json-agents-verification

Conversation

@bokelley

@bokelley bokelley commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Resolves #1214 with documentation. The SDK can already verify a request against any of a counterparty's agents. What it lacked was a statement that VerifyOptions.agent_url is not a check, and a recipe for verifying against several agents.

  • VerifyOptions.agent_url is attribution only. The verifier copies it onto VerifiedSigner.agent_url (verifier.py:556) and never compares it with anything, so agent_url=None skips no check. Which agent signed follows from the key resolver. The field now says so.
  • New section in docs/request-signing-migration.md: "Verify against a counterparty's several agents".
  • docs/agent-identity-3-3-migration.md gains a paragraph on binding several origins to one principal, linked both ways.

There are no API changes. A VerifyOptions.acceptable_agent_urls field and a multi-agent brand.json resolver were considered and rejected:

  • The field would gate nothing the resolver doesn't already gate.
  • It would add API to the deprecated chain.
  • It would diverge from the TypeScript verifier, whose VerifyRequestOptions has no expected-agent option; that verifier takes agent_url from the resolver too.

Context: prebid/salesagent#2276 and adcp-client-python#1500, which notes it covers #1214 on the 3.3 path.

Test plan

  • I ran the recipe exactly as written in the doc (extracted from the Markdown) against a Starlette endpoint over httpx.ASGITransport, with StaticSignerKeys for three agents across two principals. All checks passed:
    • the staging and production agents of one principal are attributed correctly;
    • the set entry spelled non-canonically still matches;
    • another counterparty's valid key is request_signature_key_unknown for this principal, and the same request then verifies for its own principal, so no nonce was burned;
    • a replay is request_signature_replayed;
    • 50 concurrent requests are each attributed to their own agent.
  • make lint, make typecheck, black.
  • pytest tests/conformance/signing tests/test_brand_authz.py tests/test_signed_request_verification.py tests/test_docstring_consistency.py: 1921 passed, 2 skipped.

Refs #1214

🤖 Generated with Claude Code

VerifyOptions.agent_url read like an expected-agent check, which led an
adopter to model one agent per principal (#1214, prebid/salesagent#2276).
It is attribution only: the verifier copies it onto VerifiedSigner and
never compares it. Which agent signed follows from the key resolver.

Document that on the field, and add a recipe to the request-signing guide
for accepting any of a counterparty's agents in one verification:

- framework verification maps every agent in one SignerKeyResolver and
  each agent URL to the same principal;
- direct verify_request_signature callers wrap the resolver as the
  framework does, narrow it to the principal's canonicalized agent set at
  step 7 (no nonce claimed for a key outside it), and take the agent from
  the key resolved in that call, never from shared "last matched" state;
- the 3.3 path binds each origin to one principal through OriginBindings.

The RFC 9421 recipe is marked deprecated: AdCP 3.3 deprecates the profile
and 4.0 removes it with brand.json agents[] and jwks_uri
(adcontextprotocol/adcp#8664). No API changes.

Refs #1214

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — docs-only PR plus a 7-line docstring on VerifyOptions.agent_url.

What I checked:

  • No findings from the reviewer (no blocking or Medium).
  • high_risk is true only because src/adcp/signing/verifier.py is (modified); with zero medium-or-higher findings, the modification is presumed safe (no deletion, no sensitive-category concern).
  • gated_paths: false, no no-auto-approve team match, no prior decision.
  • Reviewer verified the migration recipe against source: agent_url copied at verifier.py:556 and never compared; jwks_resolver invoked at step 7 (verifier.py:391) before nonce insert so no nonce burned; dual-kid handling in signer_keys.py; all recipe imports resolve from adcp.signing.__all__; cross-link anchors exist; test-plan checkboxes checked.
  • docs(signing): prefix is correct — no public-API signature change, just a docstring addition, so no semver/breaking signal required.

Decision table: rows 1–8 do not fire (no critical/high, no medium, gated_paths false, no deletion, no team gate). Falls through to row 9 → approve.

@bokelley
bokelley merged commit ca164f2 into next Nov 8, 2026
41 checks passed
@bokelley
bokelley deleted the feat/1214-brand-json-agents-verification branch November 8, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant